#!/usr/bin/env python3

import os
import pickle
import shutil
import subprocess
import time
import configparser

import requests
from bs4 import BeautifulSoup

# Global variables: the root directory of SDKMAN and the directory of its package candidates
config = configparser.ConfigParser()
config_ini_file = os.path.join(os.path.dirname(__file__), '..', 'conf', 'config.ini')
config.read(config_ini_file)

# If you are a normal user, the SDK is set to ~/.sdkmanager according to the directory
SDKMAN_DIR = os.path.expanduser(config.get("normal", "sdkman_dir"))

# If you are the root user, the SDK is set to /usr/local/sdkmanager according to the directory
if os.getuid() == 0:
    SDKMAN_DIR = os.path.expanduser(config.get("system", "sdkman_dir"))

CANDIDATES_DIR = os.path.join(SDKMAN_DIR, "candidates")


class Sdkman:
    """
    The Sdkman class defines methods for installing, uninstalling, and listing installed packages
    """

    def __init__(self):

        self.jdk_download_url = "https://jdk.java.net/archive"
        self.jdk_package_suffix = "linux-x64_bin.tar.gz"
        self.jdk_repo_dict: dict = {}

        self.maven_download_url = "https://dlcdn.apache.org/maven/maven-3"
        self.maven_repo_dict: dict = {}

        self.gradle_download_url = "https://services.gradle.org/distributions/"
        self.gradle_repo_dict: dict = {}

        # If it is an ordinary user, use ~/.zshrc to configure environment variables
        self.profile_file = os.path.expanduser(config.get("normal", "profile"))

        # If root user, use /etc/profile to configure environment variables
        if os.getuid() == 0:
            self.profile_file = os.path.expanduser(config.get("system", "profile"))

        # store version of sdk as the key ,and download url of sdk as the value
        self.jdk_repo_dict_file = os.path.join(os.path.dirname(__file__), '..', 'data', 'dict_jdk')
        self.maven_repo_dict_file = os.path.join(os.path.dirname(__file__), '..', 'data', 'dict_maven')
        self.gradle_repo_dict_file = os.path.join(os.path.dirname(__file__), '..', 'data', 'dict_gradle')

        # Read data from dictionary file, fill into dict
        self.fill_dict_from_file()

    def fetch_jdk_repo(self):
        """
        Crawl all JDK versions from the website, and the corresponding download addresses,
        and save them in the file as a dictionary (read from the file when using, reducing traffic and time-consuming)
        :return:
        """

        # Send GET request for page content
        response = requests.get(self.jdk_download_url)
        soup = BeautifulSoup(response.content, "html.parser")

        # Iterate over all links in the page and print out the ones containing "linux-x64_bin.tar.gz"
        for link in soup.find_all("a"):
            href = link.get("href")
            if str(href).endswith(self.jdk_package_suffix):
                # https://download.java.net/java/GA/jdk11/9/GPL/openjdk-11.0.2_linux-x64_bin.tar.gz

                # e.g. openjdk-11.0.2_linux-x64_bin.tar.gz
                jdk_package_name = str(href).split("/")[-1]
                # e.g. openjdk-11.0.2
                jdk_version = jdk_package_name.split("_")[0]
                # e.g. 11.0.2
                jdk_version = jdk_version.split("-")[-1]

                self.jdk_repo_dict[jdk_version] = href

        # save the dictionary to a file
        with open(self.jdk_repo_dict_file, "wb") as f:
            pickle.dump(self.jdk_repo_dict, f)

    def fetch_maven_repo(self):
        """
        Crawl all Maven versions from the website, and the corresponding download addresses,
        and save them in the file as a dictionary (read from the file when used, reducing traffic and time-consuming)
        :return:
        """
        # https://dlcdn.apache.org/maven/maven-3/3.0.5/binaries/apache-maven-3.0.5-bin.tar.gz

        # Send a GET request to get the page content
        response = requests.get(self.maven_download_url)
        soup = BeautifulSoup(response.content, "html.parser")

        # Iterate over all links in the page and print out the ones containing "linux-x64_bin.tar.gz"
        for link in soup.find_all("a"):
            # 3.0.5/
            version = link.text.replace("/", "")
            if not str(version.split(".")[0]).isdigit():
                continue

            # https://dlcdn.apache.org/maven/maven-3/3.0.5/binaries/apache-maven-3.0.5-bin.tar.gz
            url = os.path.join(self.maven_download_url, version, f"binaries/apache-maven-{version}-bin.tar.gz")
            self.maven_repo_dict[version] = url

        # save the dictionary to a file
        with open(self.maven_repo_dict_file, "wb") as f:
            pickle.dump(self.maven_repo_dict, f)

    def fetch_gradle_repo(self):
        """
        Crawl all Gradle versions from the website, and the corresponding download addresses,
        and save them in the file as a dictionary (read from the file when used, reducing traffic and time-consuming)
        :return:
        """

        # https://downloads.gradle-dn.com/distributions/gradle-8.0.2-all.zip
        url = "https://downloads.gradle-dn.com/distributions"

        # Send a GET request to get the page content
        response = requests.get(self.gradle_download_url)
        soup = BeautifulSoup(response.content, "html.parser")

        # Iterate over all links in the page and print out the ones containing "-all.zip"
        package_suffix = r"-all.zip"
        for span in soup.find_all("span", {"class": "name"}):
            package_name = str(span.text)
            if package_name.endswith(package_suffix):
                version = package_name.replace(r"gradle-", "").replace(package_suffix, "")
                self.gradle_repo_dict[version] = os.path.join(url, package_name)

        # save the dictionary to a file
        with open(self.gradle_repo_dict_file, "wb") as f:
            pickle.dump(self.gradle_repo_dict, f)

        print()
        command = input("Whether to continue(Y/N): ")
        if command.strip().upper() == "Y":
            return
        else:
            os.system(exit(0))

    def fetch_sdk_now(self):
        """
        Synchronize relevant SDKs (JDK/MAVEN/Gradle) from the Internet and save to a file
        :return:
        """
        print(f"Synchronization from the official website will be slow, please be patient...\n")
        start_time = time.time()

        self.fetch_jdk_repo()
        print("JDK sync complete ...")
        self.fetch_maven_repo()
        print("Maven sync complete ...")
        self.fetch_gradle_repo()
        print("Gradle sync complete ...")

        end_time = time.time()
        elapsed_time = end_time - start_time

        print(f"All SDKs are synchronized, total time (seconds)：{elapsed_time}")

        print()
        command = input("是否继续(Y/N): ")
        if command.strip().upper() == "Y":
            return
        else:
            os.system(exit(0))

    def fill_dict_from_file(self):
        """
        Read the dictionary file that saves the SDK version/download address information into memory
        :return:
        """
        with open(self.jdk_repo_dict_file, "rb") as f:
            self.jdk_repo_dict = pickle.load(f)
        with open(self.maven_repo_dict_file, "rb") as f:
            self.maven_repo_dict = pickle.load(f)
        with open(self.gradle_repo_dict_file, "rb") as f:
            self.gradle_repo_dict = pickle.load(f)

    def print_options(self, candidate: str):
        """
        Get available versions of the SDK
        :param candidate: SDK e.g. java/maven/gradle
        :return:
        """
        target_dict = {}

        if candidate.strip().upper() == "JAVA":
            target_dict = self.jdk_repo_dict
        elif candidate.strip().upper() == "MAVEN":
            target_dict = self.maven_repo_dict
        elif candidate.strip().upper() == "GRADLE":
            target_dict = self.gradle_repo_dict
        else:
            print(f"\033[31mPlz input correct candidate.\033[0m")

        for key, val in target_dict.items():
            print(key)

        print()
        command = input("Whether to continue(Y/N): ")
        if command.strip().upper() == "Y":
            return
        else:
            os.system(exit(0))

    def install(self, candidate, version):
        """
        Install/Switch SDKs
        1) According to the given SDK name/version, install the corresponding SDK
        2) If it has been installed before (already in the installation (candidates) directory),
           switch to the default SDK according to the given SDK name/version (update environment variables)
        :param candidate: e.g. java / maven /gradle
        :param version: e.g. 11
        :return: void
        """
        url = None
        # get download url by given version of sdk
        if candidate.strip().upper() == "JAVA":
            url = self.jdk_repo_dict.get(version)
        elif candidate.strip().upper() == "MAVEN":
            url = self.maven_repo_dict.get(version)
        elif candidate.strip().upper() == "GRADLE":
            url = self.gradle_repo_dict.get(version)

        # check if url is None
        if url is None:
            print()
            print(f"\033[31mCandidate [{candidate}] or version [{version}] is invalid.\033[0m")
            return

        # Download software package
        filename = os.path.basename(url)
        filepath = os.path.join("/tmp", filename)

        if not os.path.exists(filepath):
            os.system(f"axel -n {config.get('normal', 'axel_thread')} -o {filepath} {url} ")

        # release  package to target directory
        # /home/david/.sdkmanager/candidates/maven/3.8.7
        target_dir = os.path.join(CANDIDATES_DIR, candidate, version)

        # different formant of compress package , e.g. .tar.gz or .zip
        unzip_cmd = f"unzip  {filepath} -d {target_dir}"
        tar_cmd = f"tar -zxf {filepath} -C {target_dir}"

        basename = None

        # install package
        if os.path.exists(filepath) and filepath.endswith(r".tar.gz"):
            os.makedirs(target_dir, exist_ok=True)
            os.system(tar_cmd)
        elif os.path.exists(filepath) and filepath.endswith(r".zip"):
            os.makedirs(target_dir, exist_ok=True)
            os.system(unzip_cmd)
        else:
            # 1) If the downloaded package file does not exist, but the previously decompressed file exists,
            #    update the HOME environment variable to the current path
            # 2) If the downloaded package file does not exist, and there is no uncompressed file before,
            #    it means that the download and installation file failed.
            # apache-maven-3.8.7
            basename = os.listdir(target_dir)[0]
            if not os.path.exists(basename):
                print(f"\033[31mFailed to download [{filename}]\033[0m")
                return

        basename = os.listdir(target_dir)[0]
        # /home/david/.sdkmanager/candidates/maven/3.8.7/apache-maven-3.8.7
        sdk_home = os.path.join(target_dir, basename)
        # e.g. JAVA_HOME or MAVEN_HOME
        env_var = candidate.strip().upper() + "_HOME"
        os.environ[env_var] = sdk_home

        # Check if env_var exists in PATH
        if env_var in os.environ['PATH']:
            print(f"{env_var} already exists in PATH.")
            return

        # Update configuration file
        with open(self.profile_file, 'r') as f:
            profile_content = f.readlines()

        has_sdk_home = False

        with open(self.profile_file, 'w') as f:
            # Replace old(existing) SDK environment variables
            for line in profile_content:
                if line.startswith(f'export {env_var}='):
                    f.write(f"export {env_var}={sdk_home}\n")
                    has_sdk_home = True
                    continue
                # The PATH environment variable is skipped and processed at the end
                if line.startswith("export PATH="):
                    continue
                f.write(line)

            # There is no SDK environment variable before, add a line here
            if has_sdk_home is False:
                f.write(f"export {env_var}={sdk_home}\n")

        # Finally, deal with the PATH environment variable,
        # and splicing all SDK "_HOME" environment variables into PATH
        with open(self.profile_file, 'r+') as f:
            env_path_var = "export PATH=$PATH"
            for line in f.readlines():
                if "_HOME" in line:
                    env = (line.split("=")[0]).replace("export ", "")
                    env_path_var = f"{env_path_var}:${env}/bin"

            f.write(env_path_var)

        # Reload configuration
        subprocess.run(['.', self.profile_file], shell=True)

        print(f"{env_var} has been added to PATH.")
        print()
        self.list_installed()

    def uninstall(self, candidate, version):
        """
        Uninstall SDK(JDK/Maven/Gradle)
        :param candidate:
        :param version:
        :return:
        """
        # Check if package exists
        candidate_dir = os.path.join(CANDIDATES_DIR, candidate, version)
        if not os.path.exists(candidate_dir):
            print(f"{candidate} {version} is not installed")
            return

        # uninstall package
        shutil.rmtree(candidate_dir)
        print(f"{candidate} {version} uninstalled successfully.")
        print()
        self.list_installed()

    def list_installed(self):
        """
        list all installed packages
        :return:
        """
        print()
        print("===============currently installed sdk.======================")
        candidate_dir = os.path.join(CANDIDATES_DIR)
        os.system(f"tree -L 3 {candidate_dir}")
        print("=============================================================")

        print()
        command = input("Whether to continue(Y/N): ")
        if command.strip().upper() == "Y":
            return
        else:
            os.system(exit(0))
